package mud.player;

import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.swing.SwingUtilities;

import mud.Combatable;
import mud.Interactable;
import mud.Observer;
import mud.World;
import mud.events.AttackEvent;
import mud.events.Event;
import mud.events.LookEvent;
import mud.events.TurnEvent;
import mud.unit.Action;
import mud.unit.Character;
import mud.unit.PassEvent;
import terminal.Terminal;

/** Handles communication between the server and the telnet client */
public class Player implements Observer {

    /** the logger */
    private static Logger log = Logger.getLogger(Player.class.getName());

    /** the remote client terminal */
    private final Terminal terminal;

    /** the unit controlled via this console */
    private final List<PlayerCharacter> pcs;

    /** used to identify and ignore duplicate notifications */
    private Event lastEvent;
    
    /**
     * Constructor
     * @param terminal the remote client terminal
     */
    public Player(Terminal terminal) {
        this.terminal = terminal;
        this.pcs = new ArrayList<>();
	}

    @Override
    public String toString() {
        assert SwingUtilities.isEventDispatchThread();
        return getClass().getSimpleName() + "["
            + "pcs=" + pcs + "]";
    }

    /**
     * Handles the client login process
     * @param world the world into which the new unit will enter
     * @throws EOFException if the connection is closed 
     * @throws IOException if an I/O error occurs
     * @throws InterruptedException if the swing thread is interrupted while
     *         blocking
     * @throws InvocationTargetException if an exception occurs on the swing
     *         thread while blocking
     */
    public void handleLogin(final World world) throws EOFException, IOException, 
        InvocationTargetException, InterruptedException {
        
        terminal.println("By what name shall your avatar be known?");
        
        final String input = terminal.readLine().trim();
        log.info("Received: \"" + input + "\" from " + terminal);

        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                PlayerCharacter pc = new PlayerCharacter(Player.this, terminal);
                pc.setName(input);
                pc.enter(world.getNewPlayerEntrance());
                pc.setRace(world.getRaces().get(0)); // FIXME
                pcs.add(pc);
                
                log.info("Login: " + pc);
                
                updatePrompt();
            }
        });
    }

    /**
     * Parses an input string and executes the resulting command 
     * @throws EOFException if the connection is closed 
     * @throws IOException if an I/O error occurs
     * @throws InterruptedException if the swing thread is interrupted while
     *         blocking
     * @throws InvocationTargetException if an exception occurs on the swing
     *         thread while blocking
     */
    public void handleCommand() throws EOFException, IOException, 
        InvocationTargetException, InterruptedException {
        
        final String input = terminal.readLine().trim();
        log.info("Received: \"" + input + "\" from " + terminal);

        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                String[] inputs = input.split(" ", 2);
                
                // The first character is the default target of commands
                PlayerCharacter pc = pcs.get(0);
                
                // The first word may be the target character of the command
                for (PlayerCharacter c : pcs) {
                    if (c.isIdentifiedBy(inputs[0])) {
                        pc = c;
                        inputs = inputs[1].split(" ", 2);
                        break;
                    }
                }

                String order = inputs[0];
                String args = "";
                if (inputs.length > 1) {
                    args = inputs[1];
                }
        
                Command command = Command.fromString(order);
                
                if (command != null) {
                    Action action = command.interpret(pc, order, args);
                    if (action != null) {
                        pc.performAction(action);
                    }
                } else if (order.isEmpty()) {
                    // ignore
                } else {
                    terminal.println("Unknown command: \"" + order + "\"");  
                }
            }
        });
    }

    @Override
    public void observe(Event event) {
        
        if (event == lastEvent) {
            return;
        }
        lastEvent = event;
        
        if (event instanceof LookEvent) {
            LookEvent lookEvent = (LookEvent) event;
            Interactable target = lookEvent.getTarget();
            Interactable source = lookEvent.getSource();
            
            if (source instanceof Character) {
                Character unit = (Character) source;
                if (target.equals(unit.getRoom())) {
                    return;
                }
            }
            terminal.println(getIdentifier(source) 
                + " looks at " + getIdentifier(target) + ".");
            
        } else if (event instanceof AttackEvent) {
            AttackEvent attackEvent = (AttackEvent) event;
            terminal.println(getIdentifier(attackEvent.getSource())
                + " attacks "  + getIdentifier(attackEvent.getTarget())
                + " for " + attackEvent.getDamage() + " damage.");
            updatePrompt();
            
        } else if (event instanceof TurnEvent) {
            TurnEvent turnEvent = (TurnEvent) event;
            Combatable combatant = turnEvent.getNextCombatant();
            if (pcs.contains(combatant)) {
                terminal.println("It is now " + getIdentifier(combatant) 
                    + "'s turn.");
            }
        } else if (event instanceof PassEvent) {
            PassEvent passEvent = (PassEvent) event;
            terminal.println(getIdentifier(passEvent.getCombatant()) + " passes.");
        }   
    }

    /**
     * Returns a string that identifies the given entity to this player
     * @param entity the entity to identify
     * @return a string that identifies the given entity
     */
    protected String getIdentifier(Interactable entity) {
        String name = entity.getName();
        if (name != null) {
            return name;
        }
        return "something";
    }

    /**
     * Updates the prompt with the current character state(s)
     */
    private void updatePrompt() {
        StringBuilder builder = new StringBuilder();
        for (PlayerCharacter pc : pcs) {
            builder.append(pc.getName());
            builder.append('[');
            builder.append(pc.getHealth());
            builder.append("hp] ");
        }
        terminal.setPrompt(builder.toString());
    }
    
}
